/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";

import { KDNode } from "../../../common/KDTreeFuncs";
import Vector, { toVector } from "../../../common/Vector";

import { txtToMatrix, mdRender} from "../../../common/Common";

import Figure2D from "../../../visual/Figure2D";
import { showPointsInFigure } from "../../../visual/Figure2D";
import { POINT_TYPE } from "../../../visual/Canvas2DItem";
import { showMathStringInTable } from "../../../visual/Table2D";

import TDom from "../../../common/TDom";
import TCommon from "../../../common/TCommon";

import 'katex/dist/katex.min.css';


class KDTreeFindNearest extends AlgorithmBase{
  constructor(){
    super('KDTreeFindNearest');

    const figure = new Figure2D(600, 450);
    figure.xMax = 10.0;
    figure.xMin = 0.0;
    figure.yMax = 10.0;
    figure.yMin = 0.0;
    figure.gridX = 12;
    figure.gridY = 12;
    
    this.figure = figure;

    // this.model          = {dataSet:[ ],label:[ ],alpha:10.0}

    this.descriptionUrl = `${this.homepage}/data/slm/ch3/3.3/README.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch3/3.3/dataset.txt`;
  }

  init(){ 
    this.reset();

    this.initTrainDS();
  }
  
  reset(){
    this.dataset = undefined;
    // this.trainDS = {"T":[[1,2],[2,3]]};
    this.trainDS = {};
    this.trainDS.T = undefined;
    this.model = {};
    this.testDS = {};
    this.clear();

    this.initDescription();
  }

  loadModel(json){
    if(json.TrainDS){
      let tmp = JSON.parse(json.TrainDS);
      this.trainDS.T = toVector(tmp.T, true);
    }

    if(json.Model){
      let tmp = JSON.parse(json.Model);
      let node = new KDNode();
      node.copyFromJSON(tmp.node, true);
      this.model.node = node;
    }

    if(json.TestDS){
      this.testDS.T = toVector(json.TestDS.T, true);
    }
    return this.trainDS
  }

  updateInput(){
    // 显示数据集
    if(this.trainDS){
      const T = this.testDS.T;
      const R = this.testDS.R;

      console.log(R);

      let nearestPoints = [];
      let testTable = [];
      testTable.push([String.raw`\textbf{目标点}`, String.raw`\textbf{最近点}`, String.raw`\textbf{最近距离}`]);
      console.log(T.length);
      for(var i1 = 0, il = T.length; i1 < il; i1++){
        testTable.push([]);
        testTable[i1 + 1][0] = String.raw`${T[i1]}`;
        if(R){
          // testTable[i + 1][1] = String.raw`${R[i].nearest_point}`;
          testTable[i1 + 1][1] = R[i1]['nearest_point'].toKatexString();
          // testTable[i + 1][2] = parseFloat(R[i].nearest_dist).fixed(2);
          testTable[i1 + 1][2] =String.raw`${R[i1].nearest_dist.toFixed(2)}`;
          nearestPoints.push(R[i1].nearest_point);
          this.figure.addCircle(T[i1], R[i1].nearest_point);
        }
      }

      if(this.inputDiv){
        this.inputDiv.innerHTML = '';
        this.inputDiv.appendChild(this.figure.domElement);
      }
      showMathStringInTable(this.inputDiv, testTable);
      
      showPointsInFigure(this.figure, T, 5, POINT_TYPE.X_Cross, '#ff0000');
      showPointsInFigure(this.figure, nearestPoints, 8, POINT_TYPE.Circle, '#00ff00');

      // console.log(this.trainDS)
      
    }
  }

  travel(kd_node, target, max_dist){

    const k = this.k;
    if (!kd_node){
      return {nearest_point:new Vector(k),nearest_dist:Number.MAX_VALUE, nodes_visited:0} ;
    }

    var nodes_visited = 1;
    const s = kd_node.split;
    const pivot = kd_node.dom_elt;
    let nearer_node;
    let further_node;

    if (target[s] <= pivot[s]){
      nearer_node = kd_node.left;
      further_node = kd_node.right;
    }
    else{
      nearer_node = kd_node.right;
      further_node = kd_node.left;
    }

    const temp1 = this.travel(nearer_node, target, max_dist);
    
    let nearest = temp1.nearest_point;
    let dist = temp1.nearest_dist;
    nodes_visited += temp1.nodes_visited;

    if (dist < max_dist){
      max_dist = dist;
    }
    // 判断是否相交
    let temp_dist = Math.abs(pivot[s] - target[s]);
    if( max_dist < temp_dist){
      if( dist < this.minDist ){
        this.minDist = dist;
      }
      this.travelNodes.push([kd_node.dom_elt , this.minDist]);
      return {nearest_point:nearest,nearest_dist:dist, nodes_visited:nodes_visited};
    }
    // 计算距离
    let sum = 0;
    for( let d = 0; d < k; d ++ ){
      sum += (pivot[d]-target[d])*(pivot[d]-target[d]);
    }
    temp_dist = Math.sqrt(sum);

    if (temp_dist < dist){
      nearest = pivot;
      dist = temp_dist;
      max_dist = dist; 
    }

    const temp2 = this.travel(further_node, target, max_dist);
    
    nodes_visited += temp2.nodes_visited;
    if (temp2.nearest_dist < dist){
      nearest = temp2.nearest_point;
      dist = temp2.nearest_dist;
    }

    if( dist < this.minDist ){
      this.minDist = dist;
    }

    this.travelNodes.push([kd_node.dom_elt , this.minDist]);
    return {nearest_point:nearest,nearest_dist:dist, nodes_visited:nodes_visited};
  }

  find_nearest(tree, point){
    this.travelNodes  = [];
    this.minDist      = Number.MAX_VALUE;
    this.k            = point.length;
    return this.travel(tree, point, this.minDist);
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    // this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    TDom.createButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));
    // this.playButton = this.addButton(this.domElementList.input, "训练动画", this.trainAnimation.bind(this));
    // this.playButton.hidden = true;

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    TDom.getDataFromUrl(this.descriptionUrl, (txt)=>{
      mdRender(txt, this.descriptionDiv);
    })

  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    fetch(url).then((response)=>{
      return response.json();
    }).then((txt)=>{
      this.trainDS = this.loadModel(txt);
      this.updateInput();
    })

  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

  // 训练
  train(){
    const T = this.testDS.T;
    const tree = this.model.node;
    let result = [];
    let testLog = [];
    testLog.push([
      String.raw`\textbf{目标点}`, 
      String.raw`\textbf{搜索点}`, 
      String.raw`\textbf{步骤}`, 
      String.raw`\textbf{当前最近距离}`]);
    T.forEach(element => {
      let nearest = this.find_nearest(tree, element);
      for(let i = 0, il=this.travelNodes.length; i < il; i++){
        // console.log(this.travelNodes[i]);
        const node = new Vector(this.travelNodes[i][0], true);
        const dist = this.travelNodes[i][1].toFixed(5);
        testLog.push([
          element.toKatexString(), 
          node.toKatexString(), 
          `${i+1}`, 
          `${dist}`]);
      }
      nearest.nearest_point = new Vector(nearest.nearest_point, true);
      result.push(nearest);
      testLog.push(['----','----','----','----']);
    });

    if(this.trainLogDiv) this.trainLogDiv.innerHTML = '';
    showMathStringInTable(this.trainLogDiv, testLog);
    this.testDS.R = result;

    console.log(this.testDS.R);

    if(this.figure) this.figure.clearAll();

    this.updateInput()

  }
}


export default KDTreeFindNearest;